use crate::ast::*;
use crate::error::{CompilerError, Result};
use crate::llvm_backend::LLVMBackend;
use inkwell::types::BasicTypeEnum;
use inkwell::values::BasicValueEnum;
use inkwell::AddressSpace;

impl<'ctx> LLVMBackend<'ctx> {
    pub(in crate::llvm_backend) fn create_result_value(
        &mut self,
        value: BasicValueEnum<'ctx>,
        value_nrc_type: &Type,
        result_type: &crate::ast::types::ResultType,
        variant_name: &str,
    ) -> Result<BasicValueEnum<'ctx>> {
        let value_llvm_type = self.nrc_type_to_llvm_type(&Some(value_nrc_type.clone()))?;
        let result_llvm_type = self.get_result_llvm_type(result_type)?;
        let result_struct_type = match result_llvm_type {
            BasicTypeEnum::StructType(st) => st,
            _ => return Err(CompilerError::internal("Result type must be a struct")),
        };

        let result_alloca = self
            .builder
            .build_alloca(result_struct_type, "result_new")?;

        let tag_value = if variant_name == "Ok" {
            self.context.i32_type().const_int(0, false)
        } else {
            self.context.i32_type().const_int(1, false)
        };

        let tag_ptr = unsafe {
            self.builder.build_gep(
                result_struct_type,
                result_alloca,
                &[
                    self.context.i32_type().const_zero(),
                    self.context.i32_type().const_zero(),
                ],
                "tag_ptr",
            )?
        };
        self.builder.build_store(tag_ptr, tag_value)?;

        let data_ptr = unsafe {
            self.builder.build_gep(
                result_struct_type,
                result_alloca,
                &[
                    self.context.i32_type().const_zero(),
                    self.context.i32_type().const_int(1, false),
                ],
                "data_ptr",
            )?
        };

        let value_ptr = match value_llvm_type {
            BasicTypeEnum::IntType(it) => self.builder.build_alloca(it, "value_temp")?,
            BasicTypeEnum::FloatType(ft) => self.builder.build_alloca(ft, "value_temp")?,
            BasicTypeEnum::PointerType(pt) => self.builder.build_alloca(pt, "value_temp")?,
            BasicTypeEnum::StructType(st) => self.builder.build_alloca(st, "value_temp")?,
            BasicTypeEnum::VectorType(vt) => self.builder.build_alloca(vt, "value_temp")?,
            BasicTypeEnum::ArrayType(at) => self.builder.build_alloca(at, "value_temp")?,
        };
        self.builder.build_store(value_ptr, value)?;

        let data_i8_ptr = self.builder.build_pointer_cast(
            data_ptr,
            self.context.i8_type().ptr_type(AddressSpace::default()),
            "data_i8_ptr",
        )?;
        let value_i8_ptr = self.builder.build_pointer_cast(
            value_ptr,
            self.context.i8_type().ptr_type(AddressSpace::default()),
            "value_i8_ptr",
        )?;

        let memcpy_fn = self
            .module
            .get_function("llvm.memcpy.p0i8.p0i8.i64")
            .unwrap_or_else(|| {
                let void_type = self.context.void_type();
                let fn_type = void_type.fn_type(
                    &[
                        self.context
                            .i8_type()
                            .ptr_type(AddressSpace::default())
                            .into(),
                        self.context
                            .i8_type()
                            .ptr_type(AddressSpace::default())
                            .into(),
                        self.context.i64_type().into(),
                        self.context.bool_type().into(),
                    ],
                    false,
                );
                self.module
                    .add_function("llvm.memcpy.p0i8.p0i8.i64", fn_type, None)
            });

        let value_size = self.get_llvm_type_size(value_llvm_type);
        let _ = self.builder.build_call(
            memcpy_fn,
            &[
                data_i8_ptr.into(),
                value_i8_ptr.into(),
                self.context
                    .i64_type()
                    .const_int(value_size as u64, false)
                    .into(),
                self.context.bool_type().const_int(0, false).into(),
            ],
            "memcpy",
        )?;

        let result_value =
            self.builder
                .build_load(result_struct_type, result_alloca, "result_new_value")?;
        Ok(result_value)
    }


    /// Extract tag from Result value
    /// Result<T, E> is represented as { i32 tag, [N x i8] data }
    /// tag = 0 for Ok, tag = 1 for Err
    pub(in crate::llvm_backend) fn extract_result_tag(
        &mut self,
        result_value: BasicValueEnum<'ctx>,
    ) -> Result<inkwell::values::IntValue<'ctx>> {
        if let BasicValueEnum::StructValue(struct_val) = result_value {
            // Extract first field (tag)
            let tag_value = self
                .builder
                .build_extract_value(struct_val, 0, "result_tag")?;
            Ok(tag_value.into_int_value())
        } else {
            Err(CompilerError::internal("Result value must be a struct"))
        }
    }


    /// Extract Ok value from Result
    /// Returns the value stored in the data field, cast to Ok type
    pub(in crate::llvm_backend) fn extract_result_ok_value(
        &mut self,
        result_value: BasicValueEnum<'ctx>,
        result_type: &crate::ast::types::ResultType,
    ) -> Result<BasicValueEnum<'ctx>> {
        // Store result_value to stack so we can get a pointer
        let result_llvm_type = self.get_result_llvm_type(result_type)?;
        let result_ptr = self.builder.build_alloca(result_llvm_type, "result_temp")?;
        self.builder.build_store(result_ptr, result_value)?;

        // Get pointer to data field (index 1)
        let data_ptr = unsafe {
            self.builder.build_gep(
                result_llvm_type,
                result_ptr,
                &[
                    self.context.i32_type().const_zero(),
                    self.context.i32_type().const_int(1, false),
                ],
                "result_data_ptr",
            )?
        };

        // Cast data pointer to Ok type pointer
        let ok_llvm_type = self.nrc_type_to_llvm_type(&Some(*result_type.ok_type.clone()))?;
        let ok_ptr_type = match ok_llvm_type {
            inkwell::types::BasicTypeEnum::IntType(t) => t.ptr_type(AddressSpace::default()),
            inkwell::types::BasicTypeEnum::FloatType(t) => t.ptr_type(AddressSpace::default()),
            inkwell::types::BasicTypeEnum::PointerType(t) => t,
            inkwell::types::BasicTypeEnum::StructType(t) => t.ptr_type(AddressSpace::default()),
            inkwell::types::BasicTypeEnum::ArrayType(t) => t.ptr_type(AddressSpace::default()),
            inkwell::types::BasicTypeEnum::VectorType(t) => t.ptr_type(AddressSpace::default()),
        };
        let ok_ptr = self
            .builder
            .build_pointer_cast(data_ptr, ok_ptr_type, "ok_ptr")?;

        // Load the Ok value
        let ok_value = self.builder.build_load(ok_llvm_type, ok_ptr, "ok_value")?;
        Ok(ok_value)
    }


    /// Extract Err value from Result
    /// Returns the value stored in the data field, cast to Err type
    pub(in crate::llvm_backend) fn extract_result_err_value(
        &mut self,
        result_value: BasicValueEnum<'ctx>,
        result_type: &crate::ast::types::ResultType,
    ) -> Result<BasicValueEnum<'ctx>> {
        // Store result_value to stack so we can get a pointer
        let result_llvm_type = self.get_result_llvm_type(result_type)?;
        let result_ptr = self.builder.build_alloca(result_llvm_type, "result_temp")?;
        self.builder.build_store(result_ptr, result_value)?;

        // Get pointer to data field (index 1)
        let data_ptr = unsafe {
            self.builder.build_gep(
                result_llvm_type,
                result_ptr,
                &[
                    self.context.i32_type().const_zero(),
                    self.context.i32_type().const_int(1, false),
                ],
                "result_data_ptr",
            )?
        };

        // Cast data pointer to Err type pointer
        let err_llvm_type = self.nrc_type_to_llvm_type(&Some(*result_type.err_type.clone()))?;
        let err_ptr_type = match err_llvm_type {
            inkwell::types::BasicTypeEnum::IntType(t) => t.ptr_type(AddressSpace::default()),
            inkwell::types::BasicTypeEnum::FloatType(t) => t.ptr_type(AddressSpace::default()),
            inkwell::types::BasicTypeEnum::PointerType(t) => t,
            inkwell::types::BasicTypeEnum::StructType(t) => t.ptr_type(AddressSpace::default()),
            inkwell::types::BasicTypeEnum::ArrayType(t) => t.ptr_type(AddressSpace::default()),
            inkwell::types::BasicTypeEnum::VectorType(t) => t.ptr_type(AddressSpace::default()),
        };
        let err_ptr = self
            .builder
            .build_pointer_cast(data_ptr, err_ptr_type, "err_ptr")?;

        // Load the Err value
        let err_value = self
            .builder
            .build_load(err_llvm_type, err_ptr, "err_value")?;
        Ok(err_value)
    }

}
